package concurrent.lock.spin;

import org.junit.Test;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * SpinLock（自旋锁）是指尝试获取锁的线程不会立即阻塞，而是采用循环的方式去尝试获取锁，
 * 这样的好处是减少线程上下文切换的消耗，缺点是循环会消耗CPU
 */
public class SpinLockTest {

	/**
	 * 泛型是Thread，原子引用线程
	 */
	AtomicReference<Thread> reference = new AtomicReference<>();


	/**
	 * 获取锁
	 */
	public void myLock() {
		//获取当前线程
		Thread thread = Thread.currentThread();
		System.out.println(thread.getName() + "开始尝试获取锁");

		//期望值是null，如果不是null，则进入自旋
		while (!reference.compareAndSet(null, thread)) {
			try {
				TimeUnit.MILLISECONDS.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(thread.getName() + "进入自旋，等待获取锁。。。");
		}
		System.out.println(thread.getName() + "获取到锁");
	}

	/**
	 * 释放锁
	 */
	public void myUnlock() {
		//获取当前线程
		Thread thread = Thread.currentThread();
		reference.compareAndSet(thread, null);
		System.out.println(thread.getName() + "释放锁");
	}

	/**
	 * t1开始尝试获取锁
	 * t1获取到锁
	 * t2开始尝试获取锁
	 * t2进入自旋，等待获取锁。。。
	 * t2进入自旋，等待获取锁。。。
	 * t2进入自旋，等待获取锁。。。
	 * t2进入自旋，等待获取锁。。。
	 * t2进入自旋，等待获取锁。。。
	 * t2进入自旋，等待获取锁。。。
	 * t2进入自旋，等待获取锁。。。
	 * t2进入自旋，等待获取锁。。。
	 * t2进入自旋，等待获取锁。。。
	 * t1释放锁
	 * t2进入自旋，等待获取锁。。。
	 * t2获取到锁
	 * t2释放锁
	 */
	@Test
	public void test() {
		SpinLockTest spinLock = new SpinLockTest();
		new Thread(() -> {
			//获取锁
			spinLock.myLock();
			try {
				TimeUnit.SECONDS.sleep(2);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//释放锁
			spinLock.myUnlock();
		}, "t1").start();

		// 让main线程暂停1秒，使得t1线程，先执行
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		// 1秒后，启动t2线程，开始占用这个锁
		new Thread(() -> {

			// 开始占有锁
			spinLock.myLock();
			// 开始释放锁
			spinLock.myUnlock();

		}, "t2").start();

		// 让main线程暂停1秒，使得t1线程，先执行
		try {
			TimeUnit.SECONDS.sleep(3);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}
